module RecurUnroll

const T0 = Tuple{}

Base.@pure argtail(x, rest...) = rest
tail(x) = argtail(x...)

reduce_unrolled(reducer, ::T0) = Base._empty_reduce_error()
reduce_unrolled(reducer, args) = reduce_unrolled(
    reducer,
    first(args),
    tail(args)
)
reduce_unrolled(reducer, default, args::T0) = default

export reduce_unrolled
"""
```jldoctest
julia> using RecurUnroll

julia> using TypedBools

julia> using Base.Test: @inferred

julia> @inferred reduce_unrolled(&, (True(), False(), True()))
TypedBools.False()

```
"""
reduce_unrolled(reducer, default, args) =
    reducer(default, reduce_unrolled(reducer, first(args), tail(args)))

export getindex_unrolled

const T0 = Tuple{}

export getindex_unrolled
getindex_unrolled(into::T0, switch::T0) = ()
getindex_unrolled(into, switch::T0) = ()
getindex_unrolled(into::T0, switch) = ()
"""
    getindex_unrolled(into, switch)

Get items in `into` where `switch` is true.

```jldoctest
julia> using RecurUnroll, TypedBools, Base.Test

julia> @inferred getindex_unrolled((1, "a", 1.0), (True(), False(), True()))
(1, 1.0)
```
"""
function getindex_unrolled(into, switch)
    next = getindex_unrolled(tail(into), tail(switch))
    ifelse(first(switch), (first(into), next...), next)
end

export setindex_many_unrolled
"""
    setindex_many_unrolled(old, new, switch, default = nothing)

Fill `old` with `new` where `switch` is true. If you run out of new values,
fill with default instead.

```jldoctest
julia> using RecurUnroll, TypedBools, Base.Test

julia> @inferred setindex_many_unrolled(
            (1, "a", 1.0),
            (1,),
            (True(), False(), True()),
            0
        )
(1, "a", 0)
```
"""
setindex_many_unrolled(::T0, ::T0, ::T0, default = nothing) = ()
setindex_many_unrolled(::T0, ::T0, switch, default = nothing) = ()
setindex_many_unrolled(::T0, new, ::T0, default = nothing) = ()
setindex_many_unrolled(::T0, new, switch, default = nothing) = ()
setindex_many_unrolled(old, ::T0, ::T0, default = nothing) = ()
setindex_many_unrolled(old, ::T0, switch, default = nothing) =
    map(old, switch) do aold, aswitch
        ifelse(aswitch, default, aold)
    end
setindex_many_unrolled(old, new, ::T0, default = nothing) = ()
function setindex_many_unrolled(old, new, switch, default = nothing)
    first_tuple, tail_tuple = ifelse(
        first(switch),
        (first(new), tail(new)),
        (first(old), new)
    )
    first_tuple, setindex_many_unrolled(
        tail(old),
        tail_tuple,
        tail(switch),
    default)...
end

export find_unrolled
find_unrolled(t) = find_unrolled(t, 1)
find_unrolled(t::T0, n) = ()

"""
```jldoctest
julia> using RecurUnroll, TypedBools, Base.Test

julia> @inferred find_unrolled((True(), False(), True()))
(1, 3)
```
"""
function find_unrolled(t, n)
    next = find_unrolled(tail(t), n + 1)
    if Bool(first(t))
        (n, next...)
    else
        next
    end
end

end
